#!/usr/bin/perl -w

use strict;
use warnings;

die "ENV GL_RC not set\n" unless $ENV{GL_RC};
die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR};

# - show fake "reflog" from gitolite server
# - recover deleted branches
# - recover from bad force pushes

# --------------------

# WARNING
# - heavily dependent on the gitolite log file format (duh!)
# - cannot recover if some other commits were made after the force push

sub usage {
    print STDERR <<'EOF';
    USAGE
        ssh git@server gl-reflog show r1 refs/heads/b1
            # shows last 10 updates to branch b1 in repo r1
        ssh git@server gl-reflog show r1 refs/heads/b1 20
            # shows last 20 entries...
        ssh git@server gl-reflog recover r1 refs/heads/b1
            # recovers the last update to b1 in r1 if it was a "+"
EOF
    exit 1;
}

usage unless (@ARGV >= 3);

# NOTES
# - the verb "recover" is used because this is expected to be used most often
#   to recover deleted branches.  Plus there's enough confusion in git land
#   caused by "reset" and "revert" I thought I should add my bit to it ;-)
# - git's internal reflog is NOT recovered, even if you recover the branch.
#   I'm good but not *that* good ;-)
# - since this program produces a log entry that satisfies it's own criteria,
#   it acts as a "toggle" for its own action for rewinds (but not for deletes)

my($cmd, $repo, $ref, $limit) = @ARGV;
$limit ||= 10;

unshift @INC, $ENV{GL_BINDIR};
require gitolite or die "parse gitolite.pm failed\n";
gitolite->import;

my ($perm, $creator) = check_access($repo);
die "you don't have read access to $repo\n" unless $perm =~ /R/;

my @logfiles = sort glob("$ENV{GL_ADMINDIR}/logs/*");

# TODO figure out how to avoid reading *all* the log files when you really
# only need the last few

our @loglines;
{
    my @f;
    local(@ARGV) = @logfiles;
    while (<>) {
        chomp;
        @f = split /\t/;
        # field 2 is the userid, 5 is W or +, 6/7 are old/new SHAs
        # 8 is reponame, 9 is refname (but all those are 1-based)
        next unless $f[3] =~ /^(git-receive-pack|gl-reflog recover) /;
        next unless $f[8];
        next unless $f[7] eq $repo;
        next unless $f[8] eq $ref;
        push @loglines, $_;
    }
}

if ( $cmd eq 'show' ) {
    my $start = @loglines - $limit;
    $start = 0 if $start < 0;
    map { print "$loglines[$_]\n" } $start .. $#loglines;

    exit 0;
}

if ( $cmd eq 'recover' ) {
    my @f = split /\t/, $loglines[$#loglines];
    die "the last push was not yours\n" unless $f[1] eq $ENV{GL_USER};
    die "the last push was not a rewind or delete\n" unless $f[4] eq '+';

    my($oldsha, $newsha) = @f[5,6];
    if ($newsha =~ /^0+$/) {
        print "recovering $repo $ref at $oldsha (was deleted)\n";
    } else {
        print "recovering $repo $ref at $oldsha (was forced to $newsha)\n";
    }
    chdir("$ENV{GL_REPO_BASE_ABS}/$repo.git");

    my $newsha2 = $newsha;
    $newsha2 = '' if $newsha =~ /^0+$/;
    system("git", "update-ref", $ref, $oldsha, $newsha2) and
        die "repo $repo, update-ref $ref $oldsha $newsha failed...\n";
    log_it("", "+\t$newsha\t$oldsha\t$repo\t$ref");
}
